Java 26-Day Course - Day 12: Polymorphism and Casting

Day 12: Polymorphism and Casting

Polymorphism is the ability for a variable of the same type to refer to objects of various forms. It’s like the word “animal” being able to refer to a dog, cat, or bird. This allows you to write flexible and extensible code.

Upcasting and Polymorphism

Storing a child object in a parent type variable is called upcasting. It happens automatically.

class Animal {
    String name;

    Animal(String name) {
        this.name = name;
    }

    void speak() {
        System.out.println(name + " makes a sound.");
    }

    void eat() {
        System.out.println(name + " is eating.");
    }
}

class Dog extends Animal {
    Dog(String name) { super(name); }

    @Override
    void speak() {
        System.out.println(name + ": Woof woof!");
    }

    void fetch() {
        System.out.println(name + " fetches the ball.");
    }
}

class Cat extends Animal {
    Cat(String name) { super(name); }

    @Override
    void speak() {
        System.out.println(name + ": Meow~");
    }

    void scratch() {
        System.out.println(name + " extends its claws.");
    }
}

class Bird extends Animal {
    Bird(String name) { super(name); }

    @Override
    void speak() {
        System.out.println(name + ": Tweet tweet!");
    }

    void fly() {
        System.out.println(name + " flies into the sky.");
    }
}

public class PolymorphismBasic {
    public static void main(String[] args) {
        // Upcasting: referencing child objects as parent type
        Animal animal1 = new Dog("Buddy");
        Animal animal2 = new Cat("Whiskers");
        Animal animal3 = new Bird("Tweety");

        // Polymorphism: same method call, but different behavior depending on the actual object
        animal1.speak(); // Buddy: Woof woof!
        animal2.speak(); // Whiskers: Meow~
        animal3.speak(); // Tweety: Tweet tweet!

        // Using polymorphism with arrays
        Animal[] animals = {animal1, animal2, animal3};
        for (Animal animal : animals) {
            animal.speak(); // Each animal's unique sound
            animal.eat();   // Common method
        }

        // animal1.fetch(); // Compile error! (Animal type doesn't have fetch)
    }
}

Downcasting and instanceof

Explicit casting is required when converting a parent type variable to a child type.

public class DowncastingExample {
    static void handleAnimal(Animal animal) {
        // Common behavior
        animal.speak();

        // Check actual type with instanceof, then downcast
        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;
            dog.fetch(); // Can use Dog-specific method
        } else if (animal instanceof Cat) {
            Cat cat = (Cat) animal;
            cat.scratch();
        } else if (animal instanceof Bird) {
            Bird bird = (Bird) animal;
            bird.fly();
        }
    }

    // Java 16+: Pattern matching instanceof
    static void handleAnimalModern(Animal animal) {
        animal.speak();

        // Type check and variable declaration in one step
        if (animal instanceof Dog dog) {
            dog.fetch();
        } else if (animal instanceof Cat cat) {
            cat.scratch();
        } else if (animal instanceof Bird bird) {
            bird.fly();
        }
    }

    public static void main(String[] args) {
        Animal[] animals = {
            new Dog("Buddy"),
            new Cat("Whiskers"),
            new Bird("Tweety")
        };

        for (Animal animal : animals) {
            System.out.println("--- " + animal.name + " ---");
            handleAnimalModern(animal);
        }

        // Incorrect downcasting causes ClassCastException
        // Animal a = new Cat("Whiskers");
        // Dog d = (Dog) a; // ClassCastException!
    }
}

Designing with Polymorphism

A practical example of designing a payment system with polymorphism.

class Payment {
    String payerName;
    long amount;

    Payment(String payerName, long amount) {
        this.payerName = payerName;
        this.amount = amount;
    }

    boolean process() {
        System.out.println("Processing default payment");
        return true;
    }

    void printReceipt() {
        System.out.println("--- Receipt ---");
        System.out.println("Payer: " + payerName);
        System.out.println("Amount: " + String.format("%,d", amount) + " won");
    }
}

class CreditCardPayment extends Payment {
    String cardNumber;

    CreditCardPayment(String payerName, long amount, String cardNumber) {
        super(payerName, amount);
        this.cardNumber = cardNumber;
    }

    @Override
    boolean process() {
        String masked = "****-****-****-" + cardNumber.substring(cardNumber.length() - 4);
        System.out.println("Credit card payment: " + masked);
        return true;
    }
}

class BankTransfer extends Payment {
    String bankName;

    BankTransfer(String payerName, long amount, String bankName) {
        super(payerName, amount);
        this.bankName = bankName;
    }

    @Override
    boolean process() {
        System.out.println(bankName + " bank transfer processing...");
        return true;
    }
}

class MobilePayment extends Payment {
    String phoneNumber;

    MobilePayment(String payerName, long amount, String phoneNumber) {
        super(payerName, amount);
        this.phoneNumber = phoneNumber;
    }

    @Override
    boolean process() {
        System.out.println("Mobile payment (Phone: " + phoneNumber + ")");
        return true;
    }
}

public class PaymentSystem {
    // Thanks to polymorphism, the same code handles any payment method
    static void processPayment(Payment payment) {
        if (payment.process()) {
            payment.printReceipt();
            System.out.println("Payment complete!\n");
        }
    }

    public static void main(String[] args) {
        Payment[] payments = {
            new CreditCardPayment("Alice", 50000, "1234-5678-9012-3456"),
            new BankTransfer("Bob", 100000, "Kookmin Bank"),
            new MobilePayment("Charlie", 15000, "010-1234-5678")
        };

        for (Payment payment : payments) {
            processPayment(payment); // All handled with the same method
        }
    }
}

Sealed Classes (Java 17+)

A feature that restricts which classes can inherit from a given class.

// sealed: only permitted subclasses can extend
sealed class Notification permits EmailNotification, SmsNotification, PushNotification {
    String recipient;
    String message;

    Notification(String recipient, String message) {
        this.recipient = recipient;
        this.message = message;
    }

    void send() {
        System.out.println("Sending notification: " + message);
    }
}

final class EmailNotification extends Notification {
    String subject;

    EmailNotification(String recipient, String message, String subject) {
        super(recipient, message);
        this.subject = subject;
    }

    @Override
    void send() {
        System.out.println("Email sent -> " + recipient + " [" + subject + "]");
    }
}

final class SmsNotification extends Notification {
    SmsNotification(String recipient, String message) {
        super(recipient, message);
    }

    @Override
    void send() {
        System.out.println("SMS sent -> " + recipient);
    }
}

non-sealed class PushNotification extends Notification {
    PushNotification(String recipient, String message) {
        super(recipient, message);
    }

    @Override
    void send() {
        System.out.println("Push notification -> " + recipient);
    }
}

Today’s Exercises

  1. Shape Calculator: Store Circle, Rectangle, and Triangle objects in a Shape array, and use a loop to print each shape’s name and area. Use polymorphism to process them without type-specific branching.

  2. Discount Policy System: Create a DiscountPolicy parent class and implement PercentDiscount (percentage discount), FixedDiscount (fixed amount discount), and NoDiscount as child classes. Handle applyDiscount(long price) polymorphically.

  3. Zoo Simulation: Store various animal objects in an array and use instanceof (pattern matching) to execute type-specific special behaviors for each animal. Also print each animal’s food type and quantity.

Was this article helpful?